import os.path
import re

from buildbot.process import factory
from buildbot.process.factory import BuildFactory
from buildbot.process.buildstep import LogLineObserver
from buildbot.steps.source import SVN
from buildbot.steps.source import Mercurial
from buildbot.steps.shell import Compile, ShellCommand
from buildbot.steps.transfer import FileDownload
from buildbot import locks
from buildbot.steps import trigger

from config import *
try:
    from config_private import *
except ImportError:
    pass

class TestCounter(LogLineObserver):
    numTests = 0
    def outLineReceived(self, line):
        if "Number of tests" in line:
            self.step.progress.setExpectations({'tests':
                                                int(line.split(":")[-1])})
        if "Processing test" in line:
            self.numTests += 1
            self.step.setProgress('tests', self.numTests)

class TestRunner(ShellCommand):
    name = "testrunner"
    description = ["testrunner running"]
    descriptionDone = ["testrunner"]

    tot_count = skip_count = pass_count = fail_count = -1

    command = ["testrunner",
               # Uncomment to disable ua checks.
               #"--nouacheck",
               # Uncomment to run only the selected tests.
               #TESTS,
               # Uncomment the following line to get more information when
               # debugging. Warning: this slows down the testrunner a lot.
               "--debug",
               DB,
               # Uncomment the following line to limit the number of tests that
               # are run per testrunner invocation. For debugging.
               "--maxtests=10",
               SETTINGS]

    def __init__(self, *args, **kwargs):
        packages_dir = kwargs.pop("packages_dir")
        if "sources_dir" in kwargs:
            del kwargs["sources_dir"]

        self.command = self.command[:]
        self.command.extend(["--reftest-results-path",
                             os.path.join(packages_dir, "reftest_results")])
        self.command.extend(["--tests-path", os.path.join(packages_dir, "tests")])

        if REGRESS_MODE:
            # There shouldn't be any --tests argument already
            assert not "--tests" in self.command
            assert not [a for a in self.command if a.startswith("--tests=")]
            self.command.append(TESTS_REGRESS)

        ua = kwargs.pop("ua")
        self.command = self.command + ua

        ShellCommand.__init__(self, *args, **kwargs)
        counter = TestCounter()
        # XXX seems to sometimes trigger exceptions
        #self.addLogObserver('stdio', counter)

    def getText(self, cmd, results):
        count_text = "-"
        if self.tot_count >= 0:
            count_text = "%i/%i/%i/%i" % (self.tot_count, self.skip_count,
                                          self.pass_count, self.fail_count)
        return self.descriptionDone + ["tot/skip/pass/fail", count_text]

    def commandComplete(self, cmd):
        log_text = cmd.logs["stdio"].getText()
        for l in log_text.splitlines():
            m = re.search("Test results: total:(\d+) skip:(\d+) pass:(\d+) fail:(\d+)", l)
            if m:
                self.tot_count, self.skip_count, self.pass_count, self.fail_count = \
                    [int(i) for i in m.groups()]

class TestRunnerLinux(TestRunner):
    def __init__(self, *args, **kwargs):
        USE_XVFB = True
        sources_dir = kwargs.pop("sources_dir")
        if USE_XVFB:
            self.command = [os.path.join(sources_dir,
                            "browsertests", "browsertests", "runner", "xvfb_wrapper.sh"),
                            os.path.join(sources_dir,
                            "browsertests", "browsertests", "runner", "metacity_wrapper.sh"),
                            ] + self.command
        else:
            kwargs["env"] = {"DISPLAY": ":0"}

        TestRunner.__init__(self, *args, **kwargs)

class TestRunnerWindows(TestRunner):
    pass


def add_trigger_scheduler_step(factory, scheduler_name):
    factory.addStep(trigger.Trigger, schedulerNames=[scheduler_name],
                    updateSourceStamp=False, waitForFinish=True,
                    haltOnFailure=True)


class SlaveFactoryCreator(object):
    def __init__(self, slavename, target_platform, packages_dir, config,
                 browsers_updater_config=None, tests_updater_config=None):
        self._slavename = slavename
        # win lin mac
        self._target_platform = target_platform
        self._packages_dir = packages_dir
        self._sources_dir = os.path.join(packages_dir, "browsertests", "src")
        self._config = config
        self._browsers_updater_config = browsers_updater_config
        self._tests_updater_config = tests_updater_config
        self._useragents = []

        self._slave_lock = locks.SlaveLock(self._slavename + "_lock")

    def _get_tests_dir(self):
        return os.path.join(self._packages_dir, "tests")

    def _get_tests_path_option(self):
        return "--tests-path=" + self._get_tests_dir()

    def new_testrunner_factory(self, product, version):
        ua = [product, version, self._target_platform]
        factory = BuildFactory()

        args = {
            "ua": ua,
            "packages_dir": self._packages_dir,
            "sources_dir": self._sources_dir,
        }
        if self._target_platform == "win":
            factory.addStep(TestRunnerWindows, **args)
        elif self._target_platform == "lin":
            factory.addStep(TestRunnerLinux, **args)
        else:
            raise Exception("Unsupported target platform %s" % self._target_platform)

        factory.addStep(ShellCommand, command=["rsync", "-av", "reftest_results",
                                               REFTEST_UPLOAD_RSYNC_TARGET],
                                      description="rsync reftest results",
                                      workdir=self._packages_dir)
        factory.addStep(ShellCommand, command=["rm", "-rf", "reftest_results"],
                                      description="remove reftest results",
                                      workdir=self._packages_dir)
        factory.addStep(ShellCommand, command=["mkdir", "reftest_results"],
                                      workdir=self._packages_dir)
        return factory

    def new_update_sources_factory(self):
        """Update tests, BrowserTests sources, downloads settings"""
        factory = BuildFactory()

        def update_and_setup_develop(stepClass, repourl, dirname):
            factory.addStep(stepClass(repourl, mode="update", alwaysUseLatest=True,
                                      workdir=os.path.join(self._sources_dir, dirname)
                                      ))
            # "python setup.py ..." needs to be run from the directory containing setup.py
            factory.addStep(ShellCommand, command=["python", "setup.py", "develop"],
                                          description="%s develop" % dirname,
                                          workdir=os.path.join(self._sources_dir, dirname))

        USE_HG = False
        if USE_HG:
            update_and_setup_develop(Mercurial, HGREP_BROWSERTESTSMODELS, "browsertestsmodels")
            update_and_setup_develop(Mercurial, HGREP_BROWSERTESTS, "browsertests")
        else:
            update_and_setup_develop(SVN, SVNREP_BROWSERTESTSMODELS, "browsertestsmodels")
            update_and_setup_develop(SVN, SVNREP_BROWSERTESTS, "browsertests")

        factory.addStep(FileDownload, mastersrc=os.path.join("..", "configs", self._config),
                                      slavedest=os.path.join(self._sources_dir, "browsertestsmodels",
                                                             "browsertests", "bt_settings.py"))

        factory.addStep(Mercurial, repourl=HGREP_TESTS, mode="update",
                                   workdir=self._get_tests_dir(),
                                   alwaysUseLatest=True,
                        )
        return factory

    def new_browsers_updater_factory(self):
        factory = BuildFactory()
        factory.addStep(FileDownload, mastersrc=os.path.join("..", "configs", self._browsers_updater_config),
                                      slavedest="browsers_updater_config.py")
        factory.addStep(ShellCommand, command=["browsers_updtr", SETTINGS, self._get_tests_path_option(),
                                               "browsers_updater_config.py",
                                               # Uncomment for not committing results to db when debugging
                                               #"--dummy-db",
                                               "--update-status",
                                               "--debug",
                                               DB,
                                               ],
                                      description=["browsers_updater"],
                        )
        return factory

class SlaveMaintenanceFactoryCreator(SlaveFactoryCreator):
    def new_check_regress_factory(self):
        factory = BuildFactory()
        add_trigger_scheduler_step(factory, "update-sources")
        factory.addStep(FileDownload, mastersrc="../fixtures/fixture.json", slavedest="fixture.json")
        factory.addStep(ShellCommand, command=["check_regress", SETTINGS,
                                               "reset", "fixture.json",
                                               self._get_tests_path_option(), DB
                                               ],
                                      description=["check_regress reset"],
                        )
        factory.addStep(trigger.Trigger, schedulerNames=['run-tests'], updateSourceStamp=False, waitForFinish=True)
        factory.addStep(ShellCommand, command=["check_regress", SETTINGS,
                                               "check", "fixture.json",
                                               self._get_tests_path_option(), DB
                                               ],
                                      description=["check_regress check"],
                        )
        return factory

    def new_tests_updater_factory(self):
        factory = BuildFactory()
        factory.addStep(FileDownload, mastersrc=os.path.join("..", "configs", self._tests_updater_config),
                                      slavedest="tests_updater_config.py")
        factory.addStep(ShellCommand, command=["tests_updtr", SETTINGS,
                                               self._get_tests_path_option(),
                                               "tests_updater_config.py",
                                               os.path.join(self._packages_dir, "tests_update_workdir"),
                                               "--update-status",
                                               "--debug",
                                               # Uncomment for dummy mode when debugging.
                                               #"--dummy",
                                               DB
                                               ],
                                      timeout=2*60*60,
                                      description=["tests_updater"],
                        )
        return factory

    def new_testimporter_factory(self):
        factory = BuildFactory()
        factory.addStep(ShellCommand, command=["testimporter", SETTINGS,
                                               self._get_tests_path_option(), DB
                                               # Uncomment for dummy mode when debugging.
                                               #"--dummy"
                                               ],
                                      description=["testimporter"],
                                      haltOnFailure=True,
                        )
        return factory

    def new_blacklister_factory(self):
        factory = BuildFactory()
        factory.addStep(ShellCommand, command=["blacklister", SETTINGS,
                                               self._get_tests_path_option(), DB
                                               ],
                                      description=["blacklister"],
                                      haltOnFailure=True
                        )
        return factory
